﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using C1.Silverlight.Pdf;
using MyFloridaHouse.libSvBingMapsMultiShape;
using MyFloridaHouse.libSvDTO;
using MyFloridaHouse.libSvOpenDistricts.Districts;
using MyFloridaHouse.libSvOpenDistricts.MapShapes;
using System.Linq;
using MyFloridaHouse.libSvUtilities.Logging;
using MyFloridaHouse.libSvOpenDistricts.Census;


namespace MyFloridaHouse.libSvOpenDistricts.PDF
{
    public abstract partial class HouseMap : PDFdoc
    {
        // north, west, south, east
        // latmax, lonmin, latmin, lonmax

        // REGIONAL MAPS
        internal BoundingBoxDTO northwestPensacolaBoundingBoxLatLon = new BoundingBoxDTO(31.05, -87.7, 29.97, -85.2);
        internal BoundingBoxDTO northwestTallahasseeBoundingBoxLatLon = new BoundingBoxDTO(30.78, -85.52, 29.55, -83.56);
        internal BoundingBoxDTO bigBendBoundingBoxLatLon = new BoundingBoxDTO(30.55, -84.08, 28.82, -81.99);
        internal BoundingBoxDTO northeastBoundingBoxLatLon = new BoundingBoxDTO(30.59, -82.31, 29.70, -81.30);
        internal BoundingBoxDTO centralNorthBoundingBoxLatLon = new BoundingBoxDTO(29.83, -82.77, 28.37, -80.47);
        internal BoundingBoxDTO centralSouthBoundingBoxLatLon = new BoundingBoxDTO(28.79, -82.67, 26.63, -80.31);
        internal BoundingBoxDTO southeastBoundingBoxLatLon = new BoundingBoxDTO(26.957, -80.957, 25.62, -80.00);
        internal BoundingBoxDTO southBoundingBoxLatLon = new BoundingBoxDTO(27.069, -82.540, 25.573, -79.910);
        internal BoundingBoxDTO tampaBayBoundingBoxLatLon = new BoundingBoxDTO(28.33, -82.93, 27.27, -81.75);
        internal BoundingBoxDTO miamiDadeBoundingBoxLatLon = new BoundingBoxDTO(25.981, -80.874, 25.125, -80.036);

        // STATE MAP
        internal BoundingBoxDTO floridaLatLon = new BoundingBoxDTO(31.11, -87.7, 24.29, -79.8);
        internal BoundingBoxDTO goldCoastLatLon = new BoundingBoxDTO(26.785, -80.40, 25.585, -80.02);
        internal BoundingBoxDTO jacksonvilleLatLon = new BoundingBoxDTO(30.6, -82.00, 30.128, -81.329);
        internal BoundingBoxDTO orlandoLatLon = new BoundingBoxDTO(28.9, -81.75, 28.216, -80.865);
        internal BoundingBoxDTO tampaLatLon = new BoundingBoxDTO(28.175, -82.855, 27.57, -82.071);


        internal double mapBorderWidth = 0.0;
        internal double mapBorderHeight = 0.0;
        internal double mapBoundaryThickness = dotsPerInch / 18;

        internal PDFPoint mapBorderOriginCursor = new PDFPoint();

        internal double titleHeight = 0.0;  

        internal string mainTitle = "Plan Title Not Specified";
        internal string fileName = "File Name Not Specified";

        internal enumPDFPageOrientation pageOrientation = enumPDFPageOrientation.Portrait;
        internal List<HouseMapInset> insetList = new List<HouseMapInset>();
        internal List<HouseMapLabel> insetLabelList = new List<HouseMapLabel>();
        internal List<PDFClippedDistrictLabel> districtsMissingLabelsList = new List<PDFClippedDistrictLabel>();

        internal DistrictPlan districtPlan;

        internal PDF_DAL pdfDAL;

        private int _PDFinsetLayerRetrieveCount = 0;
        public int PDFinsetLayerRetrieveCount
        {
            get { return _PDFinsetLayerRetrieveCount; }
            set
            {
                if (_PDFinsetLayerRetrieveCount != value)
                {
                    _PDFinsetLayerRetrieveCount = value;
                    NotifyPropertyChanged("PDFinsetLayerRetrieveCount");
                }
            }
        }
        private int _PDFclippedDistrictRetrieveCount = 0;
        public int PDFclippedDistrictRetrieveCount
        {
            get { return _PDFclippedDistrictRetrieveCount; }
            set
            {
                if (_PDFclippedDistrictRetrieveCount != value)
                {
                    _PDFclippedDistrictRetrieveCount = value;
                    NotifyPropertyChanged("PDFclippedDistrictRetrieveCount");
                }
            }
        }

        internal bool mapGenFailure = false;

        public HouseMap(PDF_DAL pdfDAL, enumDistrictPlanPDFPrintSizes pageSize, DistrictPlan districtPlan, string mainTitle)
            : base(pageSize)
        {
            if (null == districtPlan)
            {
                throw new Exception("The district plan has not been defined, can't print plan to PDF");
            }
            else
            {
            if (districtPlan.districtList.Count <= 0)
                {
                    throw new Exception("No districts have been defined, can't print plan to PDF");
                }
                else
                {
                    this.mapGenFailure = false;
                    this.districtPlan = districtPlan;
                    this.mainTitle = mainTitle;
                    this.fileName = districtPlan.districtPlanFileName;
                    this.pdfDAL = pdfDAL;
                    this.pdfDAL.RegisterMap(this);
                }
            }

        }

        internal void HouseMapError()
        {
            mapGenFailure = true;
        }

        internal void HouseMapError(string errorMessage)
        {
            HouseMapError();
            Log.NotifyUser(errorMessage);
        }


        internal void GetDataAndDrawDocument()
        {
            if (null == pdf)
            {
                HouseMapError();
                throw new Exception("Attempted to draw map, but PDF document is not defined");
            }

            try
            {
                // set up standard widths and heights and draw title/footer
                if (enumPDFPageOrientation.Landscape == pageOrientation)
                {
                    DrawMainElementsLandscape();
                }
                else
                {
                    DrawMainElementsPortrait();
                }
            }
            catch (Exception ex)
            {
                HouseMapError();
                throw new Exception("DrawMainElements :" + ex.Message);
            }

            // set the total retrieve count before we start so we don't have a premature ending 
            // (i.e. response comes back and we think we're done before we've queued up all requests)
            PDFinsetLayerRetrieveCount = 0;
            foreach (var mapInset in insetList)
            {
                mapInset.DetermineInsetBoundaries(mapBorderOriginCursor, mapBorderWidth, mapBorderHeight);
                foreach (var lyr in mapInset.mapLayerRegionsList)
                {
                    PDFinsetLayerRetrieveCount++;
                }
            }


            int insetLayerListIndex = 0;
            foreach (var ins in insetList)
            {
                foreach (var lyr in ins.mapLayerRegionsList)
                {
                    pdfDAL.GetRegionShapesInView(insetLayerListIndex, ConvertHouseMapShapeTypeToLayerID(lyr.layerShapeType), ins.zoomLevel, ins.latLonInView);
                }
                insetLayerListIndex++;
            }

        }


        public HouseMapShapeTypes ConvertLayerIDToShapeType(int layerID)
        {
            //TODO: convert from this hard coded list of layerIDs
            switch (layerID)
            {
                case 2:
                    return HouseMapShapeTypes.counties;
                case 25:
                    return HouseMapShapeTypes.majorHighways;
                case 26:
                    return HouseMapShapeTypes.expressways;
                case 27:
                    return HouseMapShapeTypes.shorelines;
                default:
                    HouseMapError();
                    throw new Exception("Unable to convert layerID to shape HouseMapShapeTypes");
            }
        }

        public int ConvertHouseMapShapeTypeToLayerID(HouseMapShapeTypes shapeType)
        {
            //TODO: convert from this hard coded list of layerIDs
            switch (shapeType)
            {
                case  HouseMapShapeTypes.counties:
                    return 2;
                case HouseMapShapeTypes.majorHighways:
                    return 25;
                case HouseMapShapeTypes.expressways:
                    return 26;
                case HouseMapShapeTypes.shorelines:
                    return 27;

                default:
                    HouseMapError();
                    throw new Exception("Unable to convert layerID to shape HouseMapShapeTypes");
            }
        }

        internal void GetRegionShapesError(string errorMessage)
        {
            HouseMapError(errorMessage);
            PDFinsetLayerRetrieveCount--; 
        }

        internal void GetRegionShapesNoShapes()
        {
            PDFinsetLayerRetrieveCount--;
        }

        public void SaveRegionShapes(int insetListIndex, int layerID, List<RegionDTO> regionList)
        {
            bool foundMatch = false;
            // save the shapes for this inserts layer
            foreach (var lyr in insetList[insetListIndex].mapLayerRegionsList)
            {
                if (ConvertHouseMapShapeTypeToLayerID(lyr.layerShapeType) == layerID)
                {
                    lyr.regionList = regionList;
                    lyr.isRetrieved = true;
                    foundMatch = true;
                    break;
                }
            }

            if (!foundMatch)
            {
                HouseMapError("Unable to match shape layer [" + ConvertLayerIDToShapeType(layerID).ToString() + "] with list expecting shapes for print, attempting to continue");
            }

            PDFinsetLayerRetrieveCount--;


        }

        public void AttemptDrawFinalMap()
        {

            // are all shapes retrieved?
            if (PDFinsetLayerRetrieveCount <= 0)
            {
                DrawFinalMap();
            }
        }

        public void DrawFinalMap()
        {
            try
            {
                PDFShapeNameManager nameLabelManager = new PDFShapeNameManager(this);

                double districtIDFontSize = 6;
                double insertLabelFontSize = 9;
                if (enumPDFPageOrientation.Landscape != pageOrientation)
                {
                    districtIDFontSize = 20;
                    insertLabelFontSize = 27;
                }

                foreach (var mapInset in insetList)
                {
 
                    if (mapInset.showBorder)
                    {
                        pdf.DrawRectangle(HouseMapPens.houseMapOutlinePen, mapInset.insetXYRect);
                    }

                    var districtQuery = from dist in districtPlan.districtList
                                        where dist.districtID != 0
                                        select dist;


                    foreach (var district in districtQuery)
                    {
                        if (IsLatLonShapeInClipRect(MapShapeTools.ConvertServiceBoundingBoxDTOtoLibSvDTO(district.boundingBox), mapInset.latLonInView, mapInset.insetXYRect, mapInset.zoomLevel))
                        {
                            DrawDistrictInView(district, mapInset.latLonInView, mapInset.insetXYRect, mapInset.zoomLevel);

                            PDFString districtLabel = new PDFString(pdf, "Arial", districtIDFontSize, PdfFontStyle.Bold, HouseMapPens.districtLabelColor, district.districtID.ToString(), 2);
                            PointDTO districtLabelCenter = new PointDTO(district.centerPoint.latitude, district.centerPoint.longitude);
                            nameLabelManager.RegisterDistrictLabel(district.districtID, districtLabel, districtLabelCenter, MapShapeTools.ConvertServiceBoundingBoxDTOtoLibSvDTO(district.boundingBox), mapInset.latLonInView, mapInset.insetXYRect, mapInset.zoomLevel);
                        }
                    }


                    // draw other shapes & their shape names
                    try
                    {
                        foreach (var layer in mapInset.mapLayerRegionsList)
                        {
                            int regionNameFontSize = 1;

                            switch (layer.layerShapeType)
                            {
                                case HouseMapShapeTypes.counties:
                                    regionNameFontSize = (enumPDFPageOrientation.Landscape == pageOrientation) ? 6 : 12;
                                    DrawMapPolygons(nameLabelManager, mapInset, mapInset.insetXYRect, layer, regionNameFontSize, HouseMapPens.countyBoundaryBorderPen, HouseMapPens.countyBoundaryBorderFillPen, HouseMapPens.countyLabelColor);
                                    break;
                                case HouseMapShapeTypes.expressways:
                                    regionNameFontSize = 12;
                                    DrawMapPolylines(nameLabelManager, mapInset, mapInset.insetXYRect, layer, regionNameFontSize, HouseMapPens.interstateBoundaryBorderPen, HouseMapPens.interstateBoundaryBorderHighlightPen, HouseMapPens.interstateBoundaryLabelColor);
                                    break;
                                case HouseMapShapeTypes.majorHighways:
                                    regionNameFontSize = 12;
                                    DrawMapPolylines(nameLabelManager, mapInset, mapInset.insetXYRect, layer, regionNameFontSize, HouseMapPens.majorHighwayBoundaryBorderPen, HouseMapPens.majorHighwayBoundaryBorderHighlightPen, HouseMapPens.majorHighwayLabelColor);
                                    break;
                                case HouseMapShapeTypes.shorelines:
                                    regionNameFontSize = 12;
                                    DrawMapPolylines(nameLabelManager, mapInset, mapInset.insetXYRect, layer, regionNameFontSize, HouseMapPens.shorelineBoundaryBorderPen, HouseMapPens.shorelineBoundaryHighlightPen, HouseMapPens.shorelineLabelColor);
                                    break;
                                default:
                                    HouseMapError();
                                    throw new Exception("DrawFinalMap: Unable to convert layerID to shape HouseMapShapeTypes");
                            }


                        }
                    }
                    catch (Exception ex)
                    {
                        HouseMapError();
                        throw new Exception("DrawShapes :" + ex.Message);
                    }
                }

                try
                {
                    // draw inset labels
                    foreach (var mapLabel in insetLabelList)
                    {
                        PDFString name = new PDFString(pdf, "Arial", insertLabelFontSize, PdfFontStyle.Bold, Colors.Black, mapLabel.text, 2);
                        name.DrawLeftAbove(mapLabel.GetDrawingOrigin(mapBorderOriginCursor, mapBorderWidth, mapBorderHeight));
                    }

                    // draw shape labels
                    districtsMissingLabelsList = nameLabelManager.Draw();

                }
                catch (Exception ex)
                {
                    HouseMapError();
                    throw new Exception("PDF Generation Error - DrawFinalMap - Labels :" + ex.Message);
                }

            }
            catch (Exception ex)
            {
                HouseMapError();
                throw new Exception("PDF Generation Error - DrawFinalMap: " + ex.Message);
            }
            finally
            {
                if (districtsMissingLabelsList.Count == 0)
                {
                    PDFdocEvents.RaisePDFMapComplete(this, !mapGenFailure);
                }
                else
                {
                    GetClippedDistrictLabels();
                }
            }
        }


        public void GetClippedDistrictLabels()
        {
            PDFclippedDistrictRetrieveCount = 0;
            List<string> condensedDistrictList = CensusGeometryCondensed.ToCondensedList(districtPlan.districtCount);

            foreach (var districtLabel in districtsMissingLabelsList)
            {
                PDFclippedDistrictRetrieveCount++;
                pdfDAL.GetDistrictShapeClipped(districtLabel.ID, districtLabel.pdfShapeName.regionID, condensedDistrictList[districtLabel.pdfShapeName.regionID], districtLabel.pdfShapeName.mapBoundingBoxLatLon);
            }
        }


        internal void GetClippedDistrictLabeIsError(string errorMessage)
        {
            PDFclippedDistrictRetrieveCount--;
            HouseMapError(errorMessage);
            AttemptNotifyPDFMapComplete();
        }


        internal void GetClippedDistrictLabeIsNotInView()
        {
            PDFclippedDistrictRetrieveCount--;
            AttemptNotifyPDFMapComplete();
        }

        public void AttemptDrawClippedDistrictLabels(int clippedShapeID, int districtID, PointDTO labelCenterPoint)
        {
            // are all shapes retrieved?
            PDFclippedDistrictRetrieveCount--;
            
            // find the district ID in the district name list
            PDFClippedDistrictLabel districtLabel = (from x in districtsMissingLabelsList
                                          where x.ID == clippedShapeID
                                             select x).First();

            // convert from lat/lon coordinates to the particular xy coordinates for this insert
            PDFShapeName pdfShapeName = districtLabel.pdfShapeName;
            pdfShapeName.UpdateCenterPoint(labelCenterPoint);
            pdfShapeName.name.Draw(pdfShapeName.position.centerX, pdfShapeName.position.centerY, pdfShapeName.name.textBoxSize.Width, pdfShapeName.name.textBoxSize.Height);

            AttemptNotifyPDFMapComplete();
        }

        private void AttemptNotifyPDFMapComplete()
        {
            if (PDFclippedDistrictRetrieveCount <= 0)
            {
                PDFdocEvents.RaisePDFMapComplete(this, !mapGenFailure);
            }
        }

        private void DrawMapPolylines(PDFShapeNameManager nameLabelManager, HouseMapInset mapInsert, Rect clipRect, HouseMapLayerRegions layer, int regionNameFontSize, Pen layerBorderPen, Pen layerBorderHighlightPen, Color labelColor)
        {
            foreach (var region in layer.regionList)
            {
                foreach (var shape in region.mapMultiShapeDTO.shapeList)
                {
                    List<Point> exteriorRingPoints = new List<Point>();
                    foreach (var pointDTO in shape.ExteriorRing)
                    {
                        exteriorRingPoints.Add(new Point(pointDTO.longitude, pointDTO.latitude));
                    }

                    DrawPolyline(exteriorRingPoints, layerBorderPen, layerBorderHighlightPen, mapInsert.latLonInView, clipRect, mapInsert.zoomLevel);

                }
                if (null != region.name)
                {
                    PDFString regionName = new PDFString(pdf, "Arial", regionNameFontSize, PdfFontStyle.Bold, labelColor, region.name, 2);
                    PointDTO regionNameCenter = new PointDTO(region.centerLatY, region.centerLngX);
                    int layerID = ConvertHouseMapShapeTypeToLayerID(layer.layerShapeType);
                    nameLabelManager.RegisterLabel(region.regionID, layerID, regionName, regionNameCenter, region.boundingBox, mapInsert.latLonInView, clipRect, mapInsert.zoomLevel);
                }
            }
            return;
        }

        private void DrawMapPolygons(PDFShapeNameManager nameLabelManager, HouseMapInset mapInsert, Rect clipRect, HouseMapLayerRegions layer, int regionNameFontSize, Pen layerBorderPen, Pen layerFillPen, Color labelColor)
        {
            foreach (var region in layer.regionList)
            {
                foreach (var shape in region.mapMultiShapeDTO.shapeList)
                {
                    List<Point> exteriorRingPoints = new List<Point>();
                    foreach (var pointDTO in shape.ExteriorRing)
                    {
                        exteriorRingPoints.Add(new Point(pointDTO.longitude, pointDTO.latitude));
                    }
                    DrawPolygonWithFill(exteriorRingPoints, layerBorderPen, layerFillPen, mapInsert.latLonInView, clipRect, mapInsert.zoomLevel);

                }
                if (null != region.name)
                {
                    PDFString regionName = new PDFString(pdf, "Arial", regionNameFontSize, PdfFontStyle.Bold, labelColor, region.name, 2);
                    PointDTO regionNameCenter = new PointDTO(region.centerLatY, region.centerLngX);
                    int layerID = ConvertHouseMapShapeTypeToLayerID(layer.layerShapeType);
                    nameLabelManager.RegisterLabel(region.regionID, layerID, regionName, regionNameCenter, region.boundingBox, mapInsert.latLonInView, clipRect, mapInsert.zoomLevel);
                }
            }
            return;
        }



        public void DrawDistrictIDLabels()
        {
            DrawMapText();
        }

        public void DrawCityLabels()
        {
            DrawMapText();
        }


        public void DrawMapText()
        {
            // 

        }

        internal void DrawMainElementsPortrait()
        {
            docMargin = dotsPerInch / 4; //  margin is 1/4" wide
            usablePageMargin = dotsPerInch / 4;

            titleHeight = dotsPerInch * 2;  // title is 2" high;

            docFooterWidth = usablePageWidth - usablePageMargin * 2;
            docFooterHeight = dotsPerInch * 2;  // footer is 1/2" high
            docFooterMargin = dotsPerInch / 8;
            PDFPoint tempElementCursor = new PDFPoint(0, 0);
            tempElementCursor.AddXYOffset(docMargin);

            // OUTER BORDER
            PDFPoint outerBorderOriginCursor = new PDFPoint(tempElementCursor);
            pdf.DrawRectangle(HouseMapPens.borderPen, new Rect(outerBorderOriginCursor.x, outerBorderOriginCursor.y, usablePageWidth, usablePageHeight));

            // DRAW TITLE
            tempElementCursor.AddXYOffset(usablePageMargin);
            HouseMapTitle.Draw(pdf, new Rect(tempElementCursor.x, tempElementCursor.y, usablePageWidth - usablePageMargin * 2, titleHeight * .8), mainTitle, HouseMapPens.borderPen);
            tempElementCursor.AddY(titleHeight);

            // DRAW MAP BORDER
            tempElementCursor.AddY(usablePageMargin); // Gap between Title and Map Border
            mapBorderOriginCursor = new PDFPoint(tempElementCursor); // remember the mapborder origin, because we use it for other things.
            mapBorderWidth = usablePageWidth - usablePageMargin * 2;
            mapBorderHeight = usablePageHeight - usablePageMargin - titleHeight - usablePageMargin - usablePageMargin - docFooterHeight - usablePageMargin;
            //pdf.DrawRectangle(HouseMapPens.houseMapOutlinePen, new Rect(tempElementCursor.x, tempElementCursor.y, mapBorderWidth, mapBorderHeight));

            // SETUP FOR FOOTER
            PDFPoint docfooterCursor = new PDFPoint(outerBorderOriginCursor);
            docfooterCursor.AddY(usablePageHeight - docFooterHeight - usablePageMargin);  // compute in relation to bottom of page
            docfooterCursor.AddX(usablePageMargin);

            // DRAW SEAL
            double sealWidth = docFooterHeight;
            double sealHeight = sealWidth;
            PDFPoint sealCursor = new PDFPoint(docfooterCursor);
            HouseMapSeal.Draw(pdf, sealCursor, sealWidth, sealHeight);

            // DRAW LOGO            
            double logoWidth = docFooterWidth / 4 - sealWidth;  // seal and logo together are 1/4 of footer width
            double logoHeight = docFooterHeight;
            PDFPoint logoCursor = new PDFPoint(sealCursor);
            logoCursor.AddX(sealWidth + docFooterMargin);
            HouseMapLogo.Draw(pdf, logoCursor, logoWidth, logoHeight);

            // DRAW COMPASS
            double compassWidth = docFooterHeight;
            PDFPoint compassCursor = new PDFPoint(docfooterCursor);
            compassCursor.AddX(docFooterWidth /2 - compassWidth / 2);
            HouseMapCompass.Draw(pdf, compassCursor, compassWidth);

            // DRAW LEGEND
            double legendWidth = usablePageWidth / 4;
            double legendHeight = docFooterHeight;
            PDFPoint legendCursor = new PDFPoint(docfooterCursor);
            legendCursor.AddX(docFooterWidth - legendWidth );
            HouseMapLegend.Draw(pdf, legendCursor, legendWidth, legendHeight, 2);

            // DRAW FOOTER Map "Create On" date - fit in between compass & legend
            double footerCreatedOnBorderWidth = docFooterWidth / 2 - compassWidth / 2 - docFooterMargin - legendWidth - docFooterMargin;
            double footerCreatedOnBorderHeight = docFooterHeight * .3 ;
            PDFPoint footerCreatedOnCursor = new PDFPoint(docfooterCursor);
            footerCreatedOnCursor.AddX(docFooterWidth / 2 + compassWidth / 2 + docFooterMargin);
            footerCreatedOnCursor.AddY(docFooterHeight * .35);
            HouseMapFooterCreatedOn.Draw(pdf, footerCreatedOnCursor, footerCreatedOnBorderWidth, footerCreatedOnBorderHeight);

            // DRAW SCALE - fit it between the compass and the legend
            //double mileageScaleBorderWidth = (usablePageWidth / 2) - compassWidth / 2 - legendWidth - usablePageMargin * 3;
            //double mileageScaleBorderHeight = docFooterHeight;
            //PDFPoint mileageScaleCursor = new PDFPoint(docfooterCursor);
            //mileageScaleCursor.AddX((usablePageWidth - usablePageMargin * 2) / 2 + compassWidth / 2 + usablePageMargin);
            //HouseMapMileageScale.Draw(pdf, mileageScaleCursor, mileageScaleBorderWidth, mileageScaleBorderHeight);

        }


        internal void DrawMainElementsLandscape()
        {
            docMargin = dotsPerInch / 4; //  margin is 1/4" wide
            usablePageMargin = dotsPerInch / 8;

            titleHeight = dotsPerInch * .5;

            docFooterWidth = dotsPerInch * 2;
            docFooterHeight = usablePageHeight - usablePageMargin * 3; 
            docFooterMargin = dotsPerInch / 8;


            pdf.Landscape = true;
            PDFPoint tempElementCursor = new PDFPoint(0, 0);
            tempElementCursor.AddXYOffset(docMargin);

            // OUTER BORDER
            PDFPoint outerBorderOriginCursor = new PDFPoint(tempElementCursor);
            pdf.DrawRectangle(HouseMapPens.borderPen, new Rect(outerBorderOriginCursor.x, outerBorderOriginCursor.y, usablePageWidth, usablePageHeight));

            
            // DRAW TITLE
            tempElementCursor.AddXYOffset(usablePageMargin); // Gap between page border and title
            PDFPoint titleCursor = new PDFPoint(outerBorderOriginCursor);
            titleCursor.AddXYOffset(usablePageMargin);
            double titleWidth = usablePageWidth - usablePageMargin - usablePageMargin - docFooterWidth - usablePageMargin;
            HouseMapTitle.Draw(pdf, new Rect(titleCursor.x, titleCursor.y, titleWidth, titleHeight), mainTitle, HouseMapPens.borderPen);
            

            // DRAW MAP BORDER
            tempElementCursor.AddY(titleHeight);
            tempElementCursor.AddY(usablePageMargin); // Gap between Title and Map Border
            mapBorderOriginCursor = new PDFPoint(tempElementCursor); // remember the mapborder origin, because we use it for other things.
            mapBorderWidth = titleWidth;
            mapBorderHeight = usablePageHeight - usablePageMargin - titleHeight - usablePageMargin - usablePageMargin;
            //pdf.DrawRectangle(HouseMapPens.houseMapOutlinePen, new Rect(tempElementCursor.x, tempElementCursor.y, mapBorderWidth, mapBorderHeight));

            PDFPoint docFooterCursor = new PDFPoint(titleCursor);
            docFooterCursor.AddX(titleWidth + docFooterMargin);

            // DRAW SEAL - seal and logo go at top of footer and take up one quarter of footer
            double sealWidth = docFooterWidth/2;
            double sealHeight = sealWidth;
            PDFPoint sealCursor = new PDFPoint(docFooterCursor);
            sealCursor.AddX((docFooterWidth - sealWidth) / 2);
            HouseMapSeal.Draw(pdf, sealCursor, sealWidth, sealHeight);

            // DRAW LOGO            
            double logoWidth = docFooterWidth;
            double logoHeight = docFooterHeight / 4 - sealHeight;
            PDFPoint logoCursor = new PDFPoint(docFooterCursor);
            logoCursor.AddY(sealHeight + docFooterMargin);
            HouseMapLogo.Draw(pdf, logoCursor, logoWidth, logoHeight);

            // DRAW LEGEND
            double legendWidth = docFooterWidth;
            double legendHeight = docFooterHeight / 4 - docFooterMargin;
            PDFPoint legendCursor = new PDFPoint(docFooterCursor);
            legendCursor.AddY(docFooterMargin + docFooterHeight - legendHeight ); 
            HouseMapLegend.Draw(pdf, legendCursor, legendWidth, legendHeight, 1);


            // DRAW COMPASS - put compass at midpoint of footer
            double compassWidth = docFooterWidth/2;
            double compassHeight = compassWidth;
            PDFPoint compassCursor = new PDFPoint(docFooterCursor);
            compassCursor.AddX((docFooterWidth - compassWidth) / 2);
            compassCursor.AddY(docFooterHeight / 2 - compassWidth / 2);
            HouseMapCompass.Draw(pdf, compassCursor, compassWidth);



            // DRAW FILE INFO - fit in between  compass & house seal/logo
            double fileInfoBorderWidth = docFooterWidth;
            double fileInfoBorderHeight = docFooterHeight / 2 - sealHeight - docFooterMargin - logoHeight - docFooterMargin - docFooterMargin - compassHeight / 2;
            PDFPoint footerFileInfoCursor = new PDFPoint(docFooterCursor);
            footerFileInfoCursor.AddY( sealHeight + docFooterMargin + logoHeight +docFooterMargin);
        }

        internal void DrawLabelWithBottomCursor(PDFPoint cursor, string label, int fontSize)
        {
            PDFString insertLabel = new PDFString(pdf, "Arial", fontSize, PdfFontStyle.Regular, Colors.Black, label, 5);
            insertLabel.DrawLeftAbove(cursor);
        }


        private void DrawDistrictInView(District district, BoundingBoxDTO latLonInView, Rect drawingRect, double zoomLevel)
        {
            HouseMapDistrictPoints reducedDistrictPointList = BuildReducedDistrict(district, zoomLevel);

            Pen districtFillPen = new Pen(DistrictColors.GetSolidColorFromIndex(district.districtID), 1.0);
            foreach (var districtLatLonPoints in reducedDistrictPointList.districtPoints)
            {
                DrawPolygonWithFill(districtLatLonPoints, new Pen(HouseMapPens.districtBoundaryBorderColor), districtFillPen, latLonInView, drawingRect, zoomLevel);
            }
        }

        internal HouseMapDistrictPoints BuildReducedDistrict(District district, double zoomLevel)
        {
            HouseMapDistrictPoints reducedDistrictPoints = new HouseMapDistrictPoints(district.districtID);
            MapMultiShapeDTO mapMultiShapeDTO = district.mapMultiShapeDTO;
            if (null != mapMultiShapeDTO)
            {
                if (mapMultiShapeDTO.shapeList.Count > 0)
                {
                    reducedDistrictPoints = BuildReducedShape(district.districtID, mapMultiShapeDTO, zoomLevel);
                }
            }
            return reducedDistrictPoints;
        }

        internal HouseMapDistrictPoints BuildReducedShape(int districtID, MapMultiShapeDTO mapMultiShapeDTO, double zoomLevel)
        {
            HouseMapDistrictPoints exteriorShapes = new HouseMapDistrictPoints(districtID);

            MapMultiShapeDTO mapMultiShape = MapMultiShapeTools.ReducePointsByZoomLevel(mapMultiShapeDTO, zoomLevel);

            foreach (var shp in mapMultiShape.shapeList)
            {
                List<PointDTO> shapePoints = shp.ExteriorRing;

                if (shapePoints.Count > 0)
                {
                    List<Point> latLonPoints = new List<Point>();
                    foreach (var dp in shapePoints)
                    {
                        latLonPoints.Add(new Point(dp.longitude, dp.latitude));
                    }

                    exteriorShapes.districtPoints.Add(latLonPoints);
                }
            }

            return exteriorShapes;
        }


        internal static void AdjustLatLonBoundingBoxToFitSquare(BoundingBoxDTO districtBoundingBoxLatLon)
        {

            //  the map size is determined by the longitude width, then the same scale us used for latitude.
            // Adjust the district boundary to fit in the square
            double width = districtBoundingBoxLatLon.southEast.longitude - districtBoundingBoxLatLon.northWest.longitude;
            double height = districtBoundingBoxLatLon.northWest.latitude - districtBoundingBoxLatLon.southEast.latitude;
            double extraSpacePerEnd = Math.Abs(height - width) / 2;
            if (height > width)
            {
                districtBoundingBoxLatLon.southEast.longitude += extraSpacePerEnd;
                districtBoundingBoxLatLon.northWest.longitude -= extraSpacePerEnd;
            }
            else
            {
                districtBoundingBoxLatLon.northWest.latitude += extraSpacePerEnd;
                districtBoundingBoxLatLon.southEast.latitude -= extraSpacePerEnd;

            }
            width = districtBoundingBoxLatLon.southEast.longitude - districtBoundingBoxLatLon.northWest.longitude;
            height = districtBoundingBoxLatLon.northWest.latitude - districtBoundingBoxLatLon.southEast.latitude;

            // throw a margin around the adjusted boundary
            const double marginPercent = 0.2;  // buffer is padding around district so it doesn't bump the edges of the map
            double margin = (width * marginPercent);

            const double longitudeShiftEastWeight = .5; // percentage of margin to give to east
            double longitudeShiftWestWeight = 1 - longitudeShiftEastWeight;

            // the printa area is not quite square, so put space on the bottom
            const double latitudeShiftSouthWeight = .8; // percentage of margin to give to south
            double latitudeShiftNorthWeight = 1 - latitudeShiftSouthWeight;

            districtBoundingBoxLatLon.southEast.longitude += margin * longitudeShiftEastWeight;
            districtBoundingBoxLatLon.northWest.longitude -= margin * longitudeShiftWestWeight;

            districtBoundingBoxLatLon.southEast.latitude -= margin * latitudeShiftSouthWeight;
            districtBoundingBoxLatLon.northWest.latitude += margin * latitudeShiftNorthWeight;
        }

    }

}

